import * as m from 'mithril';

import { Comp, Component } from 'mithril';

///////////////////////////////////////////////////////////
// 0.
// Simplest component example - no attrs or state.
//
const comp0 = {
    view() {
        return m('span', "Test");
    }
};

// Mount the component
m.mount(document.getElementById('comp0')!, comp0);

// Unmount the component
m.mount(document.getElementById('comp0')!, null);

///////////////////////////////////////////////////////////
// 1.
// Simple example. Vnode type for component methods is inferred.
//
const comp1: Component = {
    oncreate({dom}) {
        // vnode.dom type inferred
    },
    view(vnode) {
        return m('span', "Test");
    }
};

///////////////////////////////////////////////////////////
// 2.
// Component with attrs
//
interface Comp2Attrs {
    title: string;
    description: string;
}

const comp2: Component<Comp2Attrs> = {
    view({attrs: {title, description}}) { // Comp2Attrs type is inferred
        return [m('h2', title), m('p', description)];
    }
};

// Correct use
m(comp2, {title: '', description: ''});

// Correct use with lifecycle method
m(comp2, {title: '', description: '', oncreate: (v) => `${v.attrs.title}\n${v.attrs.description}`});

// Properties missing
// $ExpectError
m(comp2, {});

// Property 'description' is missing in type '{ title: string; }'.
// $ExpectError
m(comp2, {title: ''});

// Property 'title' is missing in type '{ description: string; }'.
// $ExpectError
m(comp2, {description: ''});

// Type 'boolean' is not assignable to type 'string'.
// $ExpectError
m(comp2, {title: '', description: true});

// Object literal may only specify known properties, and 'foo' does not exist in type 'Comp2Attrs & Lifecycle<Comp2Attrs, {}> & { key?: string | number | undefined; }'.
// $ExpectError
m(comp2, {title: '', description: '', foo: ''});

///////////////////////////////////////////////////////////
// 3.
// Declares attrs type inline.
// Uses comp2 with typed attrs and makes use of `onremove`
// lifecycle method.
//
const comp3: Component<{pageHead: string}> = {
    oncreate({dom}) {
        // Can do stuff with dom
    },
    view({attrs}) {
        return m('.page',
            m('h1', attrs.pageHead),
            m(comp2,
                {
                    // attrs is type checked - nice!
                    title: "A Title",
                    description: "Some descriptive text.",
                    onremove(vnode) {
                        console.log("comp2 was removed");
                    },
                }
            ),
            // Test other hyperscript parameter variations
            m(comp1, m(comp1)),
            m('br')
        );
    }
};

///////////////////////////////////////////////////////////
// 4.
// Typed attrs and state, and `this` type is inferred.
//
interface Comp4Attrs {
    name: string;
}

interface Comp4State {
    count: number;
    add(this: Comp4State, num: number): void;
}

// Either of these two Comp4 defs will work:
type Comp4 = Component<Comp4Attrs, Comp4State> & Comp4State;
// interface Comp4 extends Component<Comp4Attrs,Comp4State>, Comp4State {}

const comp4: Comp4 = {
    count: 0, // <- Must be declared to satisfy Comp4 type which includes Comp4State type
    add(num) {
        // num and this types inferred
        this.count += num;
    },
    oninit() {
        this.count = 0;
    },
    view({attrs}) {
        return [
            m('h1', `This ${attrs.name} has been clicked ${this.count} times`),
            m('button',
                {
                    // 'this' is typed!
                    onclick: () => this.add(1)
                },
            "Click me")
        ];
    }
};

///////////////////////////////////////////////////////////
// 5.
// Stateful component (Equivalent to Comp4 example.)
// Avoids the use of `this` completely; state manipulated
// through vnode.state.
//
const comp5: Component<Comp4Attrs, Comp4State> = {
    oninit({state}) {
        state.count = 0;
        state.add = num => { state.count += num; };
    },
    view({attrs, state}) {
        return [
            m('h1', `This ${attrs.name} has been clicked ${state.count} times`),
            m('button',
                {
                    onclick() { state.add(1); }
                },
                "Click me"
            )
        ];
    }
};

///////////////////////////////////////////////////////////
//
// Concise module example with default export
//
interface Attrs {
    name: string;
}

interface State {
    count: number;
}

// Using the Comp type will apply the State intersection type for us.
const comp: Comp<Attrs, State> = {
    count: 0,
    view({attrs}) {
        return m('span', `name: ${attrs.name}, count: ${this.count}`);
    }
};
export default comp;
